package jp.seraph.jsade.core;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.commons.math.geometry.Vector3D;

import jp.seraph.jsade.model.ModelManager;
import jp.seraph.jsade.perceptor.ForceResistancePerceptor;
import jp.seraph.jsade.perceptor.GameState;
import jp.seraph.jsade.perceptor.GameStatePerceptor;
import jp.seraph.jsade.perceptor.GyroPerceptor;
import jp.seraph.jsade.perceptor.HearPerceptor;
import jp.seraph.jsade.perceptor.HingeJointPerceptor;
import jp.seraph.jsade.perceptor.SeeObject;
import jp.seraph.jsade.perceptor.SeeObjectType;
import jp.seraph.jsade.perceptor.SeePerceptor;
import jp.seraph.jsade.perceptor.TimePerceptor;

/**
 * Worldのデフォルト実装です。
 *
 *
 */
public class DefaultWorld implements World {
    public DefaultWorld(ModelManager aManager) {
        mModelManager = aManager;
        mHears = new ArrayList<HearPerceptor>();
        mSeePerceptor = new WrappedSeePerceptor();
    }

    private GameState mGameState;
    private double mGameStateTime;
    private ModelManager mModelManager;
    private double mGameTime;
    private WrappedSeePerceptor mSeePerceptor;
    private List<HearPerceptor> mHears;

    private Vector3D mUnitVectorX = null;
    private Vector3D mUnitVectorY = null;
    private Vector3D mUnitVectorZ = null;
    
    private static class WrappedSeePerceptor extends SeePerceptor{
        public WrappedSeePerceptor(){
            mCache = new HashMap<SeeObjectType, SeeObject>();
            mBase = null;
        }
        
        private Map<SeeObjectType, SeeObject> mCache;
        private SeePerceptor mBase;
        
        @Override
        public SeeObject getSeeObject(SeeObjectType type) {
            if(mBase == null)
                return new SeeObject(type, 0, 0, 0);
            
            SeeObject tResult = mBase.getSeeObject(type);
            if(tResult == null){
                if(mCache.containsKey(type)){
                    tResult = mCache.get(type);
                }else{
                    tResult = new SeeObject(type, 0,0,0);
                }
            }

            return tResult;
        }
        
        public void setBase(SeePerceptor aPerceptor){
            mBase = aPerceptor;
        }
    }

    /**
     *
     * @see jp.seraph.jsade.core.World#setUpTurn()
     */

    public void setUpTurn() {
        mModelManager.setUpTurn();
        mUnitVectorX = null;
        mUnitVectorY = null;
        mUnitVectorZ = null;
    }

    /**
     *
     * @see jp.seraph.jsade.core.World#tearDownTurn()
     */

    public void tearDownTurn() {
        mHears.clear();
        mModelManager.tearDownTurn();
    }

    public GameState getGameState() {
        return mGameState;
    }

    public double getGameStateTime() {
        return mGameStateTime;
    }

    public ModelManager getModelManager() {
        return mModelManager;
    }

    /**
     *
     * @see jp.seraph.jsade.core.World#createPlayer()
     */
    public Player createPlayer() {
        return mModelManager.createPlayer();
    }

    public SeeObject getSeeObject(SeeObjectType aType) {
        return mSeePerceptor.getSeeObject(aType);
    }

    public List<SeeObject> getSeePlayers() {
        return mSeePerceptor.getPlayers();
    }

    /**
     *
     * @see jp.seraph.jsade.core.World#getBall()
     */

    public SeeObject getBall() {
        return this.getSeeObject(SeeObjectType.BALL);
    }

    public double getTime() {
        return mGameTime;
    }

    public List<HearPerceptor> getHears() {
        return new ArrayList<HearPerceptor>(mHears);
    }

    public void visit(HingeJointPerceptor aAcceptor) {
        mModelManager.modifyModel(aAcceptor);
    }

    public void visit(GameStatePerceptor aAcceptor) {
        mGameState = aAcceptor.getState();
        mGameStateTime = aAcceptor.getTimeSpan();

        if (aAcceptor.hasTeam()) {
            mHasTeam = true;
            mIsLeft = aAcceptor.isLeft();
        }
    }

    public void visit(SeePerceptor aAcceptor) {
        mSeePerceptor.setBase(aAcceptor);
    }

    public void visit(TimePerceptor aAcceptor) {
        mGameTime = aAcceptor.getValue();
    }

    public void visit(GyroPerceptor aAcceptor) {
        mModelManager.modifyModel(aAcceptor);
    }

    /**
     *
     * @see jp.seraph.jsade.perceptor.PerceptorVisitor#visit(jp.seraph.jsade.perceptor.ForceResistancePerceptor)
     */
    public void visit(ForceResistancePerceptor aAcceptor) {
        mModelManager.modifyModel(aAcceptor);
    }

    /**
     *
     * @see jp.seraph.jsade.perceptor.PerceptorVisitor#visit(jp.seraph.jsade.perceptor.HearPerceptor)
     */

    public void visit(HearPerceptor aAcceptor) {
        mHears.add(aAcceptor);
    }

    /**
     *
     * @see jp.seraph.jsade.core.World#getUnitVectorX()
     */

    public Vector3D getUnitVectorX() {
        if (mUnitVectorX == null)
            calcUnitVectors();

        return mUnitVectorX;
    }

    /**
     *
     * @see jp.seraph.jsade.core.World#getUnitVectorY()
     */

    public Vector3D getUnitVectorY() {
        if (mUnitVectorY == null)
            calcUnitVectors();

        return mUnitVectorY;
    }

    /**
     *
     * @see jp.seraph.jsade.core.World#getUnitVectorZ()
     */

    public Vector3D getUnitVectorZ() {
        if (mUnitVectorZ == null)
            calcUnitVectors();

        return mUnitVectorZ;
    }

    private void calcUnitVectors() {
        Vector3D tF1L = this.getSeeObject(SeeObjectType.LEFT_FIELD_1).getVector();
        Vector3D tF1R = this.getSeeObject(SeeObjectType.RIGHT_FIELD_1).getVector();
        Vector3D tF2L = this.getSeeObject(SeeObjectType.LEFT_FIELD_2).getVector();

        if (tF1L == null || tF2L == null || tF1R == null) {
            tF1L = Vector3D.zero;
            tF2L = Vector3D.zero;
            tF1R = Vector3D.zero;
            return;
        }

        mUnitVectorX = tF1R.subtract(tF1L);
        if (mUnitVectorX.getNorm() != 0)
            mUnitVectorX = mUnitVectorX.normalize();

        mUnitVectorY = tF1L.subtract(tF2L);
        if (mUnitVectorY.getNorm() != 0)
            mUnitVectorY = mUnitVectorY.normalize();

        mUnitVectorZ = Vector3D.crossProduct(mUnitVectorX, mUnitVectorY);
        if (mUnitVectorZ.getNorm() != 0)
            mUnitVectorZ = mUnitVectorZ.normalize();
    }

    private boolean mHasTeam = false;
    private boolean mIsLeft = true;;

    public boolean hasTeam() {
        return mHasTeam;
    }

    public boolean isLeft() {
        return mIsLeft;
    }

    public boolean isRight() {
        return !mIsLeft;
    }

    /**
     *
     * @see jp.seraph.jsade.core.World#getMyFieldUnitVector()
     */
    public Vector3D getMyFieldUnitVector() {
        if(!hasTeam())
            return null;

        if(isLeft())
            return getUnitVectorX().negate();
        else
            return getUnitVectorX();
    }

    /**
     *
     * @see jp.seraph.jsade.core.World#getMyGoalVector()
     */
    public Vector3D getMyGoalVector() {
        if(!hasTeam())
            return null;

        Vector3D tVector1 = null;
        Vector3D tVector2 = null;
        if(isLeft()){
            tVector1 = getSeeObject(SeeObjectType.LEFT_GOAL_1).getVector();
            tVector2 = getSeeObject(SeeObjectType.LEFT_GOAL_2).getVector();
        }else{
            tVector1 = getSeeObject(SeeObjectType.RIGHT_GOAL_1).getVector();
            tVector2 = getSeeObject(SeeObjectType.RIGHT_GOAL_2).getVector();
        }

        return tVector1.add(tVector2).scalarMultiply(0.5);
    }

    /**
     *
     * @see jp.seraph.jsade.core.World#getOpponentFieldUnitVector()
     */
    public Vector3D getOpponentFieldUnitVector() {
        if(!hasTeam())
            return null;

        if(isRight())
            return getUnitVectorX().negate();
        else
            return getUnitVectorX();
    }

    /**
     *
     * @see jp.seraph.jsade.core.World#getOpponentGoalVector()
     */
    public Vector3D getOpponentGoalVector() {
        if(!hasTeam())
            return null;

        Vector3D tVector1 = null;
        Vector3D tVector2 = null;
        if(isRight()){
            tVector1 = getSeeObject(SeeObjectType.LEFT_GOAL_1).getVector();
            tVector2 = getSeeObject(SeeObjectType.LEFT_GOAL_2).getVector();
        }else{
            tVector1 = getSeeObject(SeeObjectType.RIGHT_GOAL_1).getVector();
            tVector2 = getSeeObject(SeeObjectType.RIGHT_GOAL_2).getVector();
        }

        return tVector1.add(tVector2).scalarMultiply(0.5);
    }

}
